/*
 * Site core library
 *
 * uses: util toolkit
 *       JQuery library
 *		 Hasher
 *       Raphael
 *		 JTop library (jt)				// extension
 *
 * Copyright (c) 2011 
 * Michal Skowronek (http://www.coderitual.com)
 * Licensed under the GPL
 *
 * Contact: skowronkow@gmail.com
 */

(function(global, $, jt, Hasher, undefined) {

	var document = global.document, 
	views = {},
	_modules = {},

	extensions = {},
	currentView = null,
	
	// Core object definition
	Core = function() {};

	Core.prototype = {
		
		Version: '0.4.3',

		extension: function(extensionName, extensionObject) {
			if(extensions[extensionName])
				return;
			
			if(!extensionObject) 
				return;

			if(typeof(extensionObject) === 'object') {
				extensions[extensionName] = extensionObject;	
			} else if (typeof(extensionObject) === 'function') {
				extensions[extensionName] = new extensionObject(); 
			}

			return this;
		},
		
		view: function(viewName, viewObject) {
			if(views[viewName])
				return;
				
			var view = new View(viewName)
		
			views[viewName] = view;

			view._settings = {};
			view._modules = {};
			
			view = util.extend(view, {
				onLoad: function(settings) {},
				onUnload: function() {},
				modal: false,
				parent: null,
				settings: {},
				route: '',
				query: $,
				request: $.ajax,
				createElement: util.bind(document.createElement, document),
				getElementById: util.bind(document.getElementById, document)
			}, viewObject);
			
			return view;
		},

		/*
		 * Summary: Register global module (present in all views)
		 * Arguments: Module name and constructor of module
		 */
		module: function(name, ctor) {
			if(ctor && !_modules[name]) {
				
				_modules[name] = {
					constructor: ctor,
					instance: null
				}
			
				return this;
			} else {	
				console.log('There are no constructor of module or module [' + name + '] exists');
				return false;
			}	
		},
		
		setView: function(viewName, settings) {
			var newView = views[viewName],
			settings = settings || {};
			if(!newView) {
				console.log('[SITE] There is no view ' + viewName);
				return;	
			}

			// Check if is set proper hash, and if isn't we puts it in address 
			// Important: Hasher doesn't handle change view after navigate (this is only for refresh and copy link purpose).
			// This is triggered by setting last argument of navigate to true.
			if(newView.route.length > 0 && newView.modal !== true) {
				var pathToNavigate,
					paramReg = /:(\w+)/g;
				
				pathToNavigate = newView.route.replace(paramReg, function($1, $2) {
					return settings[$2];
				});

				if(pathToNavigate !== Hasher.getFragment()) {
					Hasher.navigate(pathToNavigate);
				}
			}

			if(currentView) {
		
				var viewList = currentView.split('.');
				
				if(newView.parent) {
				
					var parentIndex = viewList.indexOf(newView.parent);
				
					for(var i = viewList.length - 1, len = parentIndex; i > len; i--) {
						views[viewList[i]].onUnload();	
						views[viewList[i]].loaded = false;
						viewList.splice(i, 1);
					}
					
					viewList.push(viewName);
					currentView = viewList.join('.');
				
				} else if(newView.modal){
					if(!newView.loaded) {
						
						if(views[viewList[viewList.length - 1]].modal) {
							views[viewList[viewList.length - 1]].onUnload();	
							views[viewList[viewList.length - 1]].loaded = false;
							viewList.splice(viewList.length - 1, 1);
						}
						
						viewList.push(viewName);
						currentView = viewList.join('.');
					}
				} else if(viewList.indexOf(viewName) > -1) { 
					
					var currentIndex = viewList.indexOf(viewName);

					for(var i = viewList.length - 1, len = currentIndex; i >= len; i--) {

						if(i !== currentIndex) {
							views[viewList[i]].onUnload();	
							views[viewList[i]].loaded = false;
							viewList.splice(i, 1);
						} else if ((JSON.stringify(settings) != JSON.stringify(views[viewList[i]]._settings))) {		// if settings are different reload
							views[viewList[i]].onUnload();	
							views[viewList[i]].loaded = false;
						}
					}
					currentView = viewList.join('.');
				
				} else {
				
					var i = viewList.length - 1;
					
					while(i >= 0) {
						views[viewList[i]].onUnload();	
						views[viewList[i]].loaded = false;
						viewList.splice(i, 1);
						i--;
					}
					
					currentView = viewName;
				}
				
			} else {
				currentView = viewName;
			}

			newView._settings = settings;
			newView.onLoad(settings);
			newView.loaded = true;
		},
		
		closeView: function(viewName) {
			
			var newView = views[viewName],
			viewList = currentView.split('.');

			if(newView && viewList.length && viewList.indexOf(viewName) ===  viewList.length - 1) {
				views[viewList[viewList.length - 1]].onUnload();	
				views[viewList[viewList.length - 1]].loaded = false;
				viewList.splice(viewList.length - 1, 1);
				currentView = viewList.join('.');
			}
		},
		
		init: function(viewName, settings) {
			if(currentView) 
				return;
		
			if(!views[viewName])
				throw '[site.init] default view must exists';
			
			var self = this,
				paramReg = Hasher.regex.ARG_NAMED,				// /:([\w\d]+)/g   eg. gets id from #/foo/bar/:id
				groupIndex = 1;

			// initialize hash routes and copy global modules
			for(var _view in views) {
				if(views.hasOwnProperty(_view)) {
					
					var view = views[_view];	

					// Copy global modules to view
					util.extend(view._modules, _modules);

					// Hasher
					(function(view, _view){
						if(view.modal !== true && view.route.length > 0) {

							Hasher.add(view.route, function() {
								
								// assign values from hash to parameters literal (named parameters object)
								var args = {},
									match,
									i = 0;

								while(match = paramReg.exec(view.route)) {
									args[match[groupIndex]] = arguments[i++];
								}

								self.setView.call(self, _view, args);
							});
						}
					}).call(this, view, _view);
				}
			}

			// Initiate hasher
			Hasher.setup();

			// Check if path fits to any route, if does ommit setting default view
			for (var i=0; i < Hasher.routes.length; i++) {
            	var r = Hasher.routes[i]['route'];
            	var	match = r.exec(Hasher.path);
        		
        		if (match) return; 	
        	}

        	this.setView(viewName, settings);
		},
		
		toString: function() {
			return 'Site core library version: ' + this.Version;
		}
	};
	
	var core = new Core(),
	
	// module sandbox
	ModuleSandbox = function(view) {
		this.bind = util.bind(view.bind, view);
		this.unbind = util.bind(view.unbind, view);
		this.trigger = util.bind(view.trigger, view);
		this.closeView = function() {
			core.closeView(view.name);
		}

		this.require = util.bind(view.require, view);
	}
	
	ModuleSandbox.prototype = {
		query: $,
		request: $.ajax,
		createElement: util.bind(document.createElement, document),
		getElementById: util.bind(document.getElementById, document),
		jtop: jt,
		util: util,
		setView: core.setView,
		openWindow: util.bind(global.open, global)
	}
	
	var View = function(name) {
		this.name = name;
		this.loaded = false;
	}
	
	View.prototype = {
		/*
		 * Summary: Register view module
		 * Arguments: Module name and constructor of module
		 */
		module: function(name, ctor) {

			if(ctor && !views[this.name]._modules[name]) {
				
				views[this.name]._modules[name] = {
					constructor: ctor,
					instance: null
				}
			
				return this;
			} else {	
				console.log('There are no constructor of module or module [' + name + '] exists');
				return false;
			}	
		},
		
		startModule: function(moduleName) {
			if(views[this.name]._modules[name] && !views[this.name]._modules[name].instance) {
				views[this.name]._modules[name].instance = new _modules[name].constructor(new Sandbox(this));
			}
			
			var module = views[this.name]._modules[name].instance;
			
			if(typeof(module.init) === 'function') {
				module.init.apply(module, arguments);
			}
		},
		
		stopModule: function(moduleName) {
			var module = views[this.name]._modules[name].instance;
			
			if(!module)
				return;
			
			if(typeof(module.destroy) === 'function') {
				module.destroy.apply(module, arguments);
			}
			
			views[this.name]._modules[name].instance = null;
		},
		
		startAll: function() {
			
			// modules creation and events binding
			for(var name in views[this.name]._modules) {
				
				if(views[this.name]._modules[name].instance)
					continue;
				
				var module = new views[this.name]._modules[name].constructor(new ModuleSandbox(this));
				views[this.name]._modules[name].instance = module;
			
			
				// Register module events handlers
				if(module.events) {
								
					for (var eventName in module.events) {
						if (module.events.hasOwnProperty(eventName)) {
							this.bind(eventName, module.events[eventName]);
						}
					}
				}
			}

			// modules initialization
			for(var name in views[this.name]._modules) {
			
				var module = views[this.name]._modules[name].instance;
				
				if(typeof(module.init) === 'function') {
					module.init.apply(module, arguments);
				}
			}
			
		},
		
		stopAll: function() {
			// _modules destroying
			for(var name in views[this.name]._modules) {
				
				var module = views[this.name]._modules[name].instance;
				
				if(!module)
					return;
				
				if(typeof(module.destroy) === 'function') {
					module.destroy.apply(module, arguments);
				}
			
				// Register module events handlers
				if(module.events) {
								
					for (var eventName in module.events) {
						if (module.events.hasOwnProperty(eventName)) {
							this.unbind(eventName, module.events[eventName]);
						}
					}
				}
				
				views[this.name]._modules[name].instance = null;
			}
		},

		require: function(extensionName) {
			if(extensions[extensionName]) {
				return extensions[extensionName];
			} else {
				throw '[view.require] extension doesn\'t exists: ' + extensionName;
			}
		}
	};
	
	// Mixin events in module
	util.events.mixin(View);		
	
	// Expose namespace
	global.site = core;

})(window, $, jt, Hasher);